package net.lgy.service.impl;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import net.lgy.config.AccountConfig;
import net.lgy.controller.req.*;
import net.lgy.dto.*;
import net.lgy.enums.BizCodeEnum;
import net.lgy.enums.ShareDateEnum;
import net.lgy.enums.ShareStatusEnum;
import net.lgy.enums.ShareTypeEnum;
import net.lgy.exception.BizException;
import net.lgy.interceptor.LoginInterceptor;
import net.lgy.mapper.AccountFileMapper;
import net.lgy.mapper.AccountMapper;
import net.lgy.mapper.ShareFileMapper;
import net.lgy.mapper.ShareMapper;
import net.lgy.model.AccountDO;
import net.lgy.model.AccountFileDO;
import net.lgy.model.ShareDO;
import net.lgy.model.ShareFileDO;
import net.lgy.service.FileService;
import net.lgy.service.ShareService;
import net.lgy.util.JwtUtil;
import net.lgy.util.SpringBeanUtil;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 分享服务实现类
 * 处理分享相关的核心业务逻辑，包括查询个人分享列表、创建分享链接和取消分享等功能
 */
@Service
@Slf4j
public class ShareServiceImpl implements ShareService {

    @Autowired
    private ShareMapper shareMapper;         // 分享主表数据访问层

    @Autowired
    private ShareFileMapper shareFileMapper; // 分享文件关联表数据访问层

    @Autowired
    private AccountFileServiceImpl accountFileService; // 账户文件服务，用于验证文件合法性

    @Autowired
    private AccountMapper accountMapper;  // 账户数据访问层，用于查询分享者信息
    @Autowired
    private AccountFileMapper accountFileMapper;  // 账户文件数据访问层，用于操作文件信息


    /**
     * 查询当前登录用户的个人分享列表
     * 从数据库查询该用户创建的所有分享记录，并转换为DTO返回
     *
     * @return 分享列表DTO集合
     */
    @Override
    public List<ShareDTO> listMyShare() {
        // 从ThreadLocal中获取当前登录用户信息
        AccountDTO accountDTO = LoginInterceptor.threadLocal.get();

        // 查询该用户创建的所有分享记录，按创建时间倒序排列
        List<ShareDO> shareDOS = shareMapper.selectList(
                new QueryWrapper<ShareDO>()
                        .eq("account_no", accountDTO.getAccountNo())
                        .orderByDesc("gmt_create")
        );

        // 将数据库实体转换为DTO并返回
        return SpringBeanUtil.copyProperties(shareDOS, ShareDTO.class);
    }

    /**
     * 创建分享链接
     * 1. 验证待分享文件的合法性
     * 2. 生成分享链接和提取码（如需）
     * 3. 计算分享过期时间
     * 4. 保存分享主记录和分享文件关联记录
     *
     * @param request 分享创建请求参数，包含文件ID列表、分享类型、有效期等信息
     * @return 包含分享详情的DTO对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class) // 事务管理，任何异常都回滚
    public ShareDTO createShare(ShareCreateRequest request) {
        // 1. 验证待分享的文件是否属于当前用户且存在
        List<Long> fileIds = request.getFileIds();
        accountFileService.checkFileIdLegal(fileIds, request.getAccountNo());

        // 2. 计算分享有效期（天数）
        Integer dayType = request.getShareDayType();
        Integer shareDay = ShareDateEnum.getDaysByType(dayType);

        // 3. 生成分享ID（雪花算法）和分享链接
        Long shareId = IdUtil.getSnowflakeNextId();
        String shareUrl = AccountConfig.PAN_FRONT_DOMAIN_SHARE_API + shareId;
        log.info("分享链接已生成:{}", shareUrl);

        // 4. 构建分享主记录对象
        ShareDO shareDO = ShareDO.builder()
                .id(shareId)
                .shareName(request.getShareName())
                .shareType(ShareTypeEnum.valueOf(request.getShareType().toUpperCase()).name())
                .shareDay(shareDay)
                .shareDayType(dayType)
                .shareUrl(shareUrl)
                .shareStatus(ShareStatusEnum.USED.name()) // 初始状态为"已使用"
                .accountNo(request.getAccountNo())
                .build();

        // 5. 设置分享过期时间
        if (ShareDateEnum.PERMANENT.getDayType().equals(dayType)) {
            // 永久有效，设置一个遥远的过期时间
            shareDO.setShareEndTime(
                    Date.from(LocalDate.of(9999, 12, 31)
                            .atStartOfDay(ZoneId.systemDefault())
                            .toInstant())
            );
        } else {
            // 临时有效，计算过期时间（当前时间 + 有效期天数）
            shareDO.setShareEndTime(
                    new Date(System.currentTimeMillis() + shareDay * 24 * 3600 * 1000L)
            );
        }

        // 6. 如需提取码，生成6位随机大写字母作为提取码
        if (ShareTypeEnum.NEED_CODE.name().equalsIgnoreCase(request.getShareType())) {
            String shareCode = RandomStringUtils.randomAlphabetic(6).toUpperCase();
            shareDO.setShareCode(shareCode);
        }

        // 7. 保存分享主记录
        int insert = shareMapper.insert(shareDO);
        log.info("保存分享主记录，shareId:{}，影响行数:{}", shareId, insert);

        // 8. 构建并保存分享文件关联记录
        List<ShareFileDO> shareFileDOS = new ArrayList<>();
        fileIds.forEach(fileId -> {
            ShareFileDO shareFileDO = ShareFileDO.builder()
                    .shareId(shareId)
                    .accountFileId(fileId)
                    .accountNo(request.getAccountNo())
                    .gmtCreate(new Date())
                    .gmtModified(new Date())
                    .build();
            shareFileDOS.add(shareFileDO);
        });
        shareFileMapper.insertBatch(shareFileDOS);
        log.info("保存分享文件关联记录，shareId:{}，文件数量:{}", shareId, fileIds.size());

        // 9. 转换为DTO并返回
        return SpringBeanUtil.copyProperties(shareDO, ShareDTO.class);
    }

    /**
     * 批量取消分享
     * 1. 验证待取消的分享是否属于当前用户
     * 2. 删除分享主记录和关联的文件记录
     *
     * @param request 取消分享请求参数，包含待取消的分享ID列表
     * @return 操作成功返回true，否则抛出异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class) // 事务管理，确保数据一致性
    public boolean cancelShare(ShareCancelRequest request) {
        // 1. 查询当前用户名下的指定分享记录
        List<ShareDO> shareDOS = shareMapper.selectList(
                new QueryWrapper<ShareDO>()
                        .eq("account_no", request.getAccountNo())
                        .in("id", request.getShareIds())
        );

        // 2. 验证查询结果：如果查询到的数量与请求数量不一致，说明存在非法分享ID
        if (shareDOS.size() != request.getShareIds().size()) {
            log.warn("取消分享失败，存在非法分享ID，accountNo:{}，请求的shareIds:{}",
                    request.getAccountNo(), request.getShareIds());
            throw new BizException(BizCodeEnum.SHARE_CANCEL_ILLEGAL);
        }

        // 3. 批量删除分享主记录
        shareMapper.deleteBatchIds(request.getShareIds());
        log.info("批量删除分享主记录，数量:{}", request.getShareIds().size());

        // 4. 批量删除分享文件关联记录
        shareFileMapper.delete(
                new QueryWrapper<ShareFileDO>()
                        .in("share_id", request.getShareIds())
        );
        log.info("批量删除分享文件关联记录，数量:{}", request.getShareIds().size());

        return true;
    }







    /**
     * 获取分享的基本信息
     * 1. 校验分享状态（是否存在、已取消或过期）
     * 2. 转换分享信息为简单DTO
     * 3. 查询并设置分享者信息
     * 4. 对于无需提取码的分享，直接生成访问令牌
     *
     * @param shareId 分享ID
     * @return 包含分享基本信息的DTO，无需提取码的分享会附带访问令牌
     */
    @Override
    public ShareSimpleDTO simpleDetail(Long shareId) {
        // 检查分享状态（通用方法，多地方复用）
        ShareDO shareDO = checkShareStatus(shareId);

        // 转换分享实体为简单DTO
        ShareSimpleDTO shareSimpleDTO = SpringBeanUtil.copyProperties(shareDO, ShareSimpleDTO.class);

        // 查询并设置分享者信息
        ShareAccountDTO shareAccountDTO = getShareAccountDTO(shareDO.getAccountNo());
        shareSimpleDTO.setShareAccountDTO(shareAccountDTO);

        // 对于无需提取码的分享，直接生成访问令牌
        if (ShareTypeEnum.NO_CODE.name().equalsIgnoreCase(shareDO.getShareType())) {
            String shareToken = JwtUtil.geneShareJWT(shareDO.getId());
            shareSimpleDTO.setShareToken(shareToken);
            return shareSimpleDTO;
        }

        // 需要提取码的分享，不返回令牌（需后续校验）
        return shareSimpleDTO;
    }

    /**
     * 校验分享码并生成访问令牌
     * 1. 根据分享ID和分享码查询有效分享
     * 2. 检查分享是否过期
     * 3. 验证通过则生成并返回访问令牌
     *
     * @param request 包含分享ID和分享码的请求对象
     * @return 访问令牌字符串，验证失败则返回null
     */
    @Override
    public String shareCheckCode(ShareCheckRequest request) {
        // 查询有效的分享记录（匹配ID、分享码和状态）
        ShareDO shareDO = shareMapper.selectOne(
                new QueryWrapper<ShareDO>()
                        .eq("id", request.getShareId())
                        .eq("share_code", request.getShareCode())
                        .eq("share_status", ShareStatusEnum.USED.name())
        );

        if (shareDO != null) {
            // 检查分享是否过期
            if (shareDO.getShareEndTime().getTime() > System.currentTimeMillis()) {
                // 未过期，生成访问令牌
                String shareToken = JwtUtil.geneShareJWT(shareDO.getId());
                return shareToken;
            } else {
                log.error("分享已过期:{}", shareDO);
                throw new BizException(BizCodeEnum.SHARE_EXPIRED);
            }
        }

        // 分享不存在或分享码错误
        return null;
    }

    /**
     * 查询分享者信息
     * 根据账户ID查询用户信息并转换为分享者DTO
     *
     * @param accountNo 分享者账户ID
     * @return 包含分享者信息的DTO
     */
    public ShareAccountDTO getShareAccountDTO(Long accountNo) {
        AccountDO accountDO = accountMapper.findByAccountNo(accountNo);
        if (accountDO == null) {
            log.error("分享者不存在:{}", accountNo);
            throw new BizException(BizCodeEnum.SHARE_NOT_EXIST);
        }
        return SpringBeanUtil.copyProperties(accountDO, ShareAccountDTO.class);
    }

    /**
     * 获取分享详情信息
     * 1. 校验分享状态
     * 2. 转换分享信息为详情DTO
     * 3. 查询并设置分享的文件列表
     * 4. 查询并设置分享者信息
     *
     * @param shareId 分享ID
     * @return 包含完整分享信息的详情DTO
     */
    @Override
    public ShareDetailDTO detail(Long shareId) {
        // 校验分享状态
        ShareDO shareDO = checkShareStatus(shareId);

        // 转换分享实体为详情DTO
        ShareDetailDTO shareDetailDTO = SpringBeanUtil.copyProperties(shareDO, ShareDetailDTO.class);

        // 查询并设置分享的文件信息
        List<AccountFileDO> accountFileDOS = getShareFileInfo(shareId);
        List<AccountFileDTO> accountFileDTOS = SpringBeanUtil.copyProperties(accountFileDOS, AccountFileDTO.class);
        shareDetailDTO.setFileDTOList(accountFileDTOS);

        // 查询并设置分享者信息
        ShareAccountDTO shareAccountDTO = getShareAccountDTO(shareDO.getAccountNo());
        shareDetailDTO.setShareAccountDTO(shareAccountDTO);

        return shareDetailDTO;
    }

    /**
     * 查看分享文件夹下的文件列表
     * 1. 校验分享状态
     * 2. 验证请求的父文件夹是否在分享范围内
     * 3. 按父文件夹ID分组，返回指定文件夹下的文件列表
     *
     * @param request 包含分享ID、父文件夹ID和分页参数的请求对象
     * @return 指定文件夹下的文件列表DTO
     */
    @Override
    public List<AccountFileDTO> listShareFile(ShareFileQueryRequest request) {
        // 校验分享状态
        ShareDO shareDO = checkShareStatus(request.getShareId());

        // 验证请求的父文件夹是否在分享范围内
        List<AccountFileDO> accountFileDOS = checkShareFileIdOnStatue(shareDO.getId(), List.of(request.getParentId()));
        List<AccountFileDTO> accountFileDTOS = SpringBeanUtil.copyProperties(accountFileDOS, AccountFileDTO.class);

        // 按父文件夹ID分组，获取指定文件夹下的子文件列表
        Map<Long, List<AccountFileDTO>> fileMap = accountFileDTOS.stream()
                .collect(Collectors.groupingBy(AccountFileDTO::getParentId));
        List<AccountFileDTO> childrenFileList = fileMap.get(request.getParentId());

        return childrenFileList;
    }

    /**
     * 转存分享文件到当前用户空间
     * 1. 校验分享状态和待转存文件的合法性
     * 2. 验证目标文件夹归属
     * 3. 递归获取所有待转存文件（包括子文件）
     * 4. 检查存储空间是否足够
     * 5. 批量复制文件到当前用户空间
     *
     * @param request 包含分享ID、待转存文件ID和目标文件夹ID的请求对象
     * @return 转存成功返回true，否则返回false
     */
    @Override
    @Transactional(rollbackFor = Exception.class)  // 事务管理，确保转存操作的原子性
    public Boolean transferShareFile(ShareFileTransferRequest request) {
        // 校验分享状态
        checkShareStatus(request.getShareId());

        // 验证待转存文件是否在分享范围内
        checkInshareFiles(request.getFileIds(), request.getShareId());

        // 验证目标文件夹是否属于当前用户
        AccountFileDO currentAccountFileDO = accountFileMapper.selectOne(
                new QueryWrapper<AccountFileDO>()
                        .eq("id", request.getParentId())
                        .eq("account_no", request.getAccountNo())
        );
        if (currentAccountFileDO == null) {
            log.error("目标文件夹不是当前用户的:{}", request.getParentId());
            throw new BizException(BizCodeEnum.SHARE_FILE_ILLEGAL);
        }

        // 获取待转存的文件实体
        List<AccountFileDO> shareAccountFileDOS = accountFileMapper.selectBatchIds(request.getFileIds());

        // 递归获取所有子文件（包括嵌套文件夹中的文件）
        List<AccountFileDO> batchCopyFileWithRecur = accountFileService
                .findBatchCopyFileWithRecur(shareAccountFileDOS, request.getParentId());

        // 将文件归属设置为当前用户
        batchCopyFileWithRecur.forEach(accountFileDO -> accountFileDO.setAccountNo(request.getAccountNo()));

        // 计算待转存文件总大小并检查存储空间
        long totalSize = batchCopyFileWithRecur.stream()
                .map(accountFileDO -> accountFileDO.getFileSize() == null ? 0 : accountFileDO.getFileSize())
                .mapToLong(Long::valueOf)
                .sum();

        if (!accountFileService.checkAndUpdateCapacity(request.getAccountNo(), totalSize)) {
            throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH);
        }

        // 批量插入文件记录
        Integer rows = accountFileMapper.insertFileBatch(batchCopyFileWithRecur);
        return rows > 0;
    }

    /**
     * 验证待转存文件是否在分享范围内
     * 1. 获取分享包含的所有文件ID
     * 2. 递归获取所有子文件ID（包括嵌套文件夹）
     * 3. 检查待转存文件是否都在分享范围内
     *
     * @param fileIds 待转存文件ID列表
     * @param shareId 分享ID
     */
    public void checkInshareFiles(List<Long> fileIds, Long shareId) {
        // 获取分享关联的文件记录
        List<ShareFileDO> shareFileDOS = shareFileMapper.selectList(
                new QueryWrapper<ShareFileDO>().eq("share_id", shareId)
        );

        // 提取分享的文件ID列表
        List<Long> shareFileIdList = shareFileDOS.stream()
                .map(ShareFileDO::getAccountFileId)
                .toList();

        // 获取分享的文件实体
        List<AccountFileDO> shareAccountFileDOSList = accountFileMapper.selectBatchIds(shareFileIdList);

        // 递归获取所有子文件（包括嵌套文件夹中的文件）
        List<AccountFileDO> allShareFiles = new ArrayList<>();
        accountFileService.findAllAccountFileDOWithRecur(allShareFiles, shareAccountFileDOSList, false);

        // 提取所有分享文件的ID
        List<Long> allShareFileIdList = allShareFiles.stream()
                .map(AccountFileDO::getId)
                .toList();

        // 检查待转存文件是否都在分享范围内
        for (Long fileId : fileIds) {
            if (!allShareFileIdList.contains(fileId)) {
                log.error("文件不在分享链接里:{}", fileId);
                throw new BizException(BizCodeEnum.SHARE_FILE_ILLEGAL);
            }
        }
    }

    /**
     * 验证文件是否在分享范围内
     * 1. 获取分享包含的所有文件（包括子文件）
     * 2. 检查目标文件ID是否都在分享范围内
     *
     * @param shareId 分享ID
     * @param fileIDList 待验证的文件ID列表
     * @return 所有分享文件的实体列表
     */
    public List<AccountFileDO> checkShareFileIdOnStatue(Long shareId, List<Long> fileIDList) {
        // 获取分享包含的所有文件
        List<AccountFileDO> shareFileInfoList = getShareFileInfo(shareId);

        // 递归获取所有子文件（包括嵌套文件夹）
        List<AccountFileDO> allAccountFileDOList = new ArrayList<>();
        accountFileService.findAllAccountFileDOWithRecur(allAccountFileDOList, shareFileInfoList, false);

        if (CollectionUtil.isEmpty(allAccountFileDOList)) {
            log.error("分享文件列表为空:{}", shareFileInfoList);
            return List.of();
        }

        // 提取所有分享文件的ID
        Set<Long> allFileIdSet = allAccountFileDOList.stream()
                .map(AccountFileDO::getId)
                .collect(Collectors.toSet());

        // 检查目标文件是否都在分享范围内
        if (!allFileIdSet.containsAll(fileIDList)) {
            log.error("分享文件不存在:{}", fileIDList);
            throw new BizException(BizCodeEnum.SHARE_FILE_ILLEGAL);
        }

        return allAccountFileDOList;
    }

    /**
     * 检查分享状态
     * 验证分享是否存在、是否已取消、是否已过期
     *
     * @param shareId 分享ID
     * @return 有效的分享实体
     */
    public ShareDO checkShareStatus(Long shareId) {
        ShareDO shareDO = shareMapper.selectById(shareId);

        // 验证分享是否存在
        if (shareDO == null) {
            log.error("分享链接不存在:{}", shareId);
            throw new BizException(BizCodeEnum.SHARE_NOT_EXIST);
        }

        // 验证分享是否已取消
        if (ShareStatusEnum.CANCELED.name().equalsIgnoreCase(shareDO.getShareStatus())) {
            log.error("分享已取消:{}", shareId);
            throw new BizException(BizCodeEnum.SHARE_CANCEL);
        }

        // 验证分享是否已过期
        if (shareDO.getShareEndTime().getTime() < System.currentTimeMillis()) {
            log.error("分享已过期:{}", shareId);
            throw new BizException(BizCodeEnum.SHARE_EXPIRED);
        }

        return shareDO;
    }

    /**
     * 根据分享ID获取所有分享的文件信息
     *
     * @param shareId 分享ID
     * @return 分享的文件实体列表
     */
    public List<AccountFileDO> getShareFileInfo(Long shareId) {
        // 获取分享关联的文件ID列表
        List<Long> shareFileIds = getShareFileIdList(shareId);

        // 查询文件实体
        return accountFileMapper.selectBatchIds(shareFileIds);
    }

    /**
     * 获取分享关联的所有文件ID
     *
     * @param shareId 分享ID
     * @return 文件ID列表
     */
    public List<Long> getShareFileIdList(Long shareId) {
        // 用 Map 接收单字段结果，key 为字段名（account_file_id）
        List<Map<String, Object>> maps = shareFileMapper.selectMaps(
                new QueryWrapper<ShareFileDO>()
                        .eq("share_id", shareId)
                        .select("account_file_id")
        );

        // 从 Map 中提取 account_file_id 并转换为 Long
        return maps.stream()
                .map(map -> Long.valueOf(map.get("account_file_id").toString()))
                .toList();
    }





}
